/* -*- C -*- */
/*
 * Copyright (c) 2020, ASR microelectronics
 * All rights reserved.
 */
#include "spi_arch.h"
#if LCD_GENERIC_SPI
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include "dma.h"
#include "../../board.h"

#define USE_QSPI                         0

#define LOG_DBG                          /*printf */
#define LOG_INFO                         /*printf */
#define LOG_WARN                         printf
#define LOG_ERR                          printf

#define IRQ_SSP0                         (3)
#define IRQ_SSP1                         (2)
#define IRQ_SSP2                         (1)

#define RX_THRESHOLD                     (8) /* Half of the FIFO Size */
#define TX_THRESHOLD                     (8) /* Half of the FIFO Size */

/* APB Clock rgister */
#define APB_CLOCK_BASE                   (0xD4015000)
#define APBC_SSP0_CLK_RST                (0x1C)
#define APBC_SSP1_CLK_RST                (0x20)
#define APBC_SSP2_CLK_RST                (0x4C)

#define FNCLKSEL(x)                      (((x) & 0x7) << 4)
#define FNCLKSEL_MASK                    (0x7 << 4)
#define RST(x)                           (((x) & 0x1) << 2)
#define FNCLK(x)                         (((x) & 0x1) << 1)
#define APBCLK(x)                        (((x) & 0x1) << 0)

/* SSP Register */
#define CRANE_SSP0_BASE                  (0xD401B000)
#define CRANE_SSP1_BASE                  (0xD42AC000)
#define CRANE_SSP2_BASE                  (0xD401C000)

#define CRANEM_SSP0_BASE                 (0xD401B000)
#define CRANEM_SSP1_BASE                 (0xD401B800)
#define CRANEM_SSP2_BASE                 (0xD401C000)

#define TOP_CTRL                         (0x0)
#define FIFO_CTRL                        (0x4)
#define INT_EN                           (0x8)
#define TO                               (0xC)
#define DATAR                            (0x10)
#define STATUS                           (0x14)
#define THREE_WIRE_CTRL                  (0x54)

#define TOP_TTELP                        (1 << 18) /* TXD Three-state Enable On Last Phase */
#define TOP_TTE                          (1 << 17) /* TXD Three-State Enable */
#define TOP_HOLD_FRAME_LOW               (1 << 14) /* Hold Frame Low Control */
#define TOP_CTRL_TRAIL                   (1 << 13) /* Trail Byte */
#define TOP_LBM                          (1 << 12) /* Loopback Mode */
#define TOP_SPH                          (1 << 11) /* Motorola SPI SSPSCLK Phase Setting */
#define TOP_SPO                          (1 << 10) /* Motorola SPI SSPSCLK Polarity Setting */
#define TOP_DSS_MASK                     (0x1F << 5)
#define TOP_DSS(x)                       (((x - 1) & 0x1F) << 5) /* Data Size Select */
#define TOP_CTRL_SFRMDIR                 (1 << 4) /* Master/Slave Mode */
#define TOP_CTRL_SCLKDIR                 (1 << 3) /* Master/Slave Mode */
#define TOP_CTRL_SSE                     (0x1)

#define FIFO_ENTRY_MAX                   (16) /* For None Packing Mode */

#define FIFO_CTRL_RXFIFO_AUTO_FULL_CTRL  (1 << 17) /* Rx FIFO Auto Full Control */
#define FIFO_CTRL_TXFIFO_WR_ENDIAN(x)    (((x) & 0x3) << 14) /* Byte Mapping Between APB pwdata and FIFO Entry for Tx */
#define FIFO_CTRL_RXFIFO_RD_ENDIAN(x)    (((x) & 0x3) << 12) /* Byte Mapping Between APB pwdata and FIFO Entry for Rx */

#define FIFO_CTRL_RSRE                   (1 << 11) /* Rx DMA Service Request Enable */
#define FIFO_CTRL_TSRE                   (1 << 10) /* Tx DMA Service Request Enable */
#define FIFO_CTRL_RFT(x)                 (((x - 1) & 0x1F) << 5) /* Rx FIFO Trigger Threshold */
#define FIFO_CTRL_TFT(x)                 ((x - 1) & 0x1F) /* Tx FIFO Trigger Threshold */
#define FIFO_CTRL_RFT_MASK               (0x1F << 5)
#define FIFO_CTRL_TFT_MASK               (0x1F)

#define INT_EN_TIM                       (1 << 5) /* Transmit FIFO Under Run Interrupt Mask*/
#define INT_EN_RIM                       (1 << 4) /* Receive FIFO Over Run Interrupt Mask */
#define INT_EN_TIE                       (1 << 3) /* Transmit FIFO Interrupt Enable */
#define INT_EN_RIE                       (1 << 2) /* Receive FIFO Interrupt Enable */
#define INT_EN_TINTE                     (1 << 1) /* Receive Timeout Interrupt Enable */

#define STATUS_RFL(x)                    (((x) >> 15) & 0x1F) /* Rx FIFO Level */
#define STATUS_RNE(x)                    (((x) >> 14) & 0x1) /* Rx FIFO Not Empty */
#define STATUS_TFL(x)                    (((x) >> 7) & 0x1F) /* Tx FIFO Level */
#define STATUS_TNF(x)                    (((x) >> 6) & 0x1) /* Tx FIFO Not Full */
#define STATUS_ROR                       (1 << 20) /* Receive FIFO Overrun */
#define STATUS_RFS                       (1 << 13) /* Receive FIFO Service Request */
#define STATUS_TFS                       (1 << 5) /* Transmit FIFO Service Request */
#define STATUS_TUR                       (1 << 12) /* Transmit FIFO Underrun */
#define STATUS_TINT                      (1 << 3) /* Receive Timeout Interrupt */
#define STATUS_BSY                       (1 << 0) /* SSP Busy */

#define THREE_WIRE_CTRL_WIDTH_DYN_CHANGE (1 << 2)
#define THREE_WIRE_CTRL_TXD_OEN          (1 << 1)
#define THREE_WIRE_CTRL_THREE_WIRE_EN    (1 << 0)

/* spi_info->work_mode */
#define WORK_MODE_POLLING                (0)
#define WORK_MODE_IRQ                    (1)
#define WORK_MODE_DMA                    (2)

/* Rx Timeout Count */
#define RX_TIMEOUT_CNT_MAX               ((1 << 24) - 1)
#define RX_TIMEOUT_CNT_1MS               (26 * 1000) /* 26MHz APB clk */
#define RX_TIMEOUT_CNT                   (200 * RX_TIMEOUT_CNT_1MS) /* 200ms */

typedef unsigned int gpio_t;
typedef void (*spi_irq_handle_t)(void *arg);

struct spi_clk_info {
  int clk_val;
  char *desc;
};

struct spi_xfer_req {
  uint8_t *in;
  const uint8_t *out;
  volatile unsigned len_in;
  volatile unsigned len_out;
  xfer_mode_t mode;
  bool cont;
};

typedef struct {
  uint32_t ssp;
  uint32_t *clk_reg;
  int gpio_cs;
  spi_clk_t clk;
  const uint32_t *mfp_cfg;
  unsigned irq;
  int bus;
  spi_wire_mode_t wire_mode;
  int txd_curr;
  unsigned fifo_dss;
  unsigned tx_fifo_thd;
  unsigned rx_fifo_thd;
  unsigned fifo_size;
  unsigned loopback_retry;
  unsigned work_mode;
  unsigned tx_dma_dev_id;
  unsigned rx_dma_dev_id;
  struct dma_op_t rx_dma_op;
  struct dma_op_t tx_dma_op;
  struct spi_xfer_req xfer_req;
  volatile unsigned error;
  spi_complete_cb_t complete_cb;
  void *cb_arg;
}spi_info_t;

#if USE_QSPI
#define GPIO_76                          (76)
#define QSPI_DAT3_MPFI                   (0x2C4)
#define QSPI_DAT2_MPFI                   (0x2C8)
#define QSPI_DAT1_MPFI                   (0x2CC)
#define QSPI_DAT0_MPFI                   (0x2D0)
#define QSPI_CLK_MPFI                    (0x2D4)
#define QSPI_CS1_MPFI                    (0x2D8)
#define QSPI_CS2_MPFI                    (0x2DC)
#define QSPI_DQM_MPFI                    (0x2E0)

static const uint32_t mfp_spi_2[] = {
  MFP_REG(QSPI_DAT3_MPFI) | MFP_DRIVE_MEDIUM | MFP_SLEEP_DIR | MFP_PULL_HIGH | MFP_AF1, /* nHold */
  MFP_REG(QSPI_DAT2_MPFI) | MFP_DRIVE_MEDIUM | MFP_SLEEP_DIR | MFP_PULL_HIGH | MFP_AF1, /* nWP */
  MFP_REG(QSPI_DAT1_MPFI) | MFP_DRIVE_MEDIUM | MFP_SLEEP_DIR | MFP_AF2, /* Rx */
  MFP_REG(QSPI_DAT0_MPFI) | MFP_DRIVE_MEDIUM | MFP_SLEEP_DIR | MFP_AF2, /* Tx */
  MFP_REG(QSPI_CLK_MPFI) | MFP_DRIVE_MEDIUM | MFP_SLEEP_DIR | MFP_AF2,  /* CLK */
  MFP_REG(QSPI_CS1_MPFI) | MFP_DRIVE_MEDIUM | MFP_SLEEP_DIR | MFP_AF1,  /* CS */
  MFP_EOC
};
#else
static const uint32_t mfp_spi_2[] = {
  MFP_REG(GPIO_12) | MFP_DRIVE_MEDIUM | MFP_SLEEP_DIR | MFP_AF1,  /* SSP2_SCLK */
  MFP_REG(GPIO_13) | MFP_DRIVE_MEDIUM | MFP_SLEEP_DIR | MFP_AF0,  /* CS */
  MFP_REG(GPIO_14) | MFP_DRIVE_MEDIUM | MFP_SLEEP_DIR | MFP_AF1,  /* SSP2_RXD */
  MFP_REG(GPIO_15) | MFP_DRIVE_MEDIUM | MFP_SLEEP_DIR | MFP_AF1,  /* SSP2_TXD */
  MFP_EOC
};
#endif

static spi_info_t spi_info[SPI_BUS_NUM] = {
  { /* 0 */
    .ssp = CRANEM_SSP0_BASE,
    .clk_reg = (uint32_t *)(APB_CLOCK_BASE + APBC_SSP0_CLK_RST),
    .mfp_cfg = NULL,
    .bus = -1, /* Not Inited */
    .gpio_cs = GPIO_INVALID,
    .irq = IRQ_SSP0,
    .tx_dma_dev_id = DMA_DEVICE_ID_SPI0_TX,
    .rx_dma_dev_id = DMA_DEVICE_ID_SPI0_RX,
  },
  { /* 1 */
    .ssp = CRANEM_SSP1_BASE,
    .clk_reg = (uint32_t *)(APB_CLOCK_BASE + APBC_SSP1_CLK_RST),
    .mfp_cfg = NULL,
    .bus = -1, /* Not Inited */
    .gpio_cs = GPIO_INVALID,
    .irq = IRQ_SSP1,
  },
  { /* 2 */
    .ssp = CRANEM_SSP2_BASE,
    .clk_reg = (uint32_t *)(APB_CLOCK_BASE + APBC_SSP2_CLK_RST),
#if USE_QSPI
    .gpio_cs = GPIO_76,
#else
    .gpio_cs = GPIO_13,
#endif
    .mfp_cfg = mfp_spi_2,
    .bus = -1, /* Not Inited */
    .irq = IRQ_SSP2,
    .tx_dma_dev_id = DMA_DEVICE_ID_SPI2_TX,
    .rx_dma_dev_id = DMA_DEVICE_ID_SPI2_RX,
  },
};

static spi_info_t *
get_spi_info(spi_bus_t bus)
{
  spi_info_t *p = NULL;

  if(bus < SPI_BUS_NUM) {
    p = &spi_info[bus];
  } else {
    LOG_ERR("[%s]: wrong bus num %d\n", __func__, bus);
  }

  return p;
}
static bool
spi_mode_3_wire_tx(spi_info_t *spi_info)
{
  return (spi_info->wire_mode == SPI_3_WIRE_MODE) && (spi_info->txd_curr == SPI_DIR_TX);
}
static int
spi_chip_sel(spi_info_t *spi_info, int state)
{
  if (spi_info->gpio_cs != GPIO_INVALID) {
    gpio_set_direction(spi_info->gpio_cs, GPIO_OUT);
    (state == 0) ? gpio_output_set(spi_info->gpio_cs, 0) : gpio_output_set(spi_info->gpio_cs, 1);
  }
  return 0;
}
static void
spi_port_enable(spi_info_t *spi_info, int enable)
{
  uint32_t top;

  top = readl(spi_info->ssp + TOP_CTRL);
  if(enable) {
    top |= TOP_CTRL_SSE;
  } else {
    top &= ~TOP_CTRL_SSE;
  }
  writel(top, spi_info->ssp + TOP_CTRL);
}
static void
_tx_int_enable(spi_info_t *spi_info, int enable)
{
  uint32_t int_en;

  /*LOG_DBG("[%s]: %d\n", __func__, enable); */

  int_en = readl(spi_info->ssp + INT_EN);
  if(enable) {
    int_en |= INT_EN_TIE;
  } else {
    int_en &= ~INT_EN_TIE;
  }
  writel(int_en, spi_info->ssp + INT_EN);
}
static void
_rx_int_enable(spi_info_t *spi_info, int enable)
{
  uint32_t int_en;

  /*LOG_DBG("[%s]: %d\n", __func__, enable); */

  int_en = readl(spi_info->ssp + INT_EN);
  if(enable) {
    int_en |= INT_EN_RIE;
  } else {
    int_en &= ~INT_EN_RIE;
  }
  writel(int_en, spi_info->ssp + INT_EN);
}
static void
spi_int_enable(spi_info_t *spi_info, int enable)
{
  _rx_int_enable(spi_info, enable);
  _tx_int_enable(spi_info, enable);
}
static void
spi_rx_timeout_enable(spi_info_t *spi_info, int enable)
{
  uint32_t int_en, top, to;
  unsigned to_cnt = RX_TIMEOUT_CNT;

  if(to_cnt > RX_TIMEOUT_CNT_MAX) {
    LOG_WARN("Rx timeout %u ms is too large, use the max value %u ms\n",
             RX_TIMEOUT_CNT / RX_TIMEOUT_CNT_1MS, RX_TIMEOUT_CNT / RX_TIMEOUT_CNT_1MS);
    to_cnt = RX_TIMEOUT_CNT_MAX;
  }

  top = readl(spi_info->ssp + TOP_CTRL);
  int_en = readl(spi_info->ssp + INT_EN);
  if(enable) {
    to = to_cnt;
    if(spi_info->work_mode == WORK_MODE_DMA) {
      /*LOG_DBG("Enable handle trailing byte by DMA\n"); */
      top |= TOP_CTRL_TRAIL;
    } else {
      /*LOG_DBG("Enable handle trailing byte by INT\n"); */
      int_en |= INT_EN_TINTE;
    }
  } else {
    to = 0;
    if(spi_info->work_mode == WORK_MODE_DMA) {
      top &= ~TOP_CTRL_TRAIL;
    } else {
      int_en &= ~INT_EN_TINTE;
    }
  }

  writel(to, spi_info->ssp + TO);
  writel(int_en, spi_info->ssp + INT_EN);
  writel(top, spi_info->ssp + TOP_CTRL);
}
static void
spi_set_xfer_endian(spi_info_t *spi_info, unsigned tx_endian, unsigned rx_endian)
{
  uint32_t val;

  val = readl(spi_info->ssp + FIFO_CTRL);
  val &= ~(FIFO_CTRL_TXFIFO_WR_ENDIAN(3) | FIFO_CTRL_RXFIFO_RD_ENDIAN(3));
  val |= FIFO_CTRL_TXFIFO_WR_ENDIAN(tx_endian) | FIFO_CTRL_RXFIFO_RD_ENDIAN(rx_endian);
  /*LOG_DBG("[%s]: val = 0x%x\n", __func__, (unsigned)val); */
  writel(val, spi_info->ssp + FIFO_CTRL);
}
static void
spi_set_fifo_dss(spi_info_t *spi_info, int dss_byte)
{
  uint32_t val;

  /*LOG_DBG("[%s]: %d byte\n", __func__, dss_byte); */
  val = readl(spi_info->ssp + TOP_CTRL);
  val &= ~TOP_DSS_MASK;
  val |= TOP_DSS(dss_byte * 8);
  writel(val, spi_info->ssp + TOP_CTRL);
  spi_info->fifo_dss = dss_byte;
}
static void
spi_set_fifo_dss_bit(spi_info_t *spi_info, int dss_bits)
{
  uint32_t val;

  /*LOG_INFO("[%s]: %d bits\n", __func__, dss_bits); */
  val = readl(spi_info->ssp + TOP_CTRL);
  val &= ~TOP_DSS_MASK;
  val |= TOP_DSS(dss_bits);
  writel(val, spi_info->ssp + TOP_CTRL);
}
static void
spi_calc_rx_fifo_thd(spi_info_t *spi_info, unsigned rx_len)
{
  unsigned val;

  val = (spi_info->fifo_dss == 4) ? (rx_len / 4) : rx_len;

  if(val > RX_THRESHOLD) {
    spi_info->rx_fifo_thd = RX_THRESHOLD;
  } else if(val == 0) {
    spi_info->rx_fifo_thd = 1;
  } else {
    spi_info->rx_fifo_thd = val;
  }
  /*LOG_DBG("Dss %u, rx_len %u, thd %d\n", spi_info->fifo_dss, rx_len, spi_info->rx_fifo_thd); */
}
static void
spi_fifo_thd_enable(spi_info_t *spi_info, int enable)
{
  uint32_t val;

  val = readl(spi_info->ssp + FIFO_CTRL);

  if(enable) {
    if(spi_info->rx_fifo_thd) {
      val |= FIFO_CTRL_RFT(spi_info->rx_fifo_thd);
    }
    if(spi_info->tx_fifo_thd) {
      val |= FIFO_CTRL_TFT(spi_info->tx_fifo_thd);
    }
  } else {
    val &= ~(FIFO_CTRL_RFT_MASK | FIFO_CTRL_TFT_MASK);
  }

  writel(val, spi_info->ssp + FIFO_CTRL);
}
static void
spi_wait_not_busy(spi_info_t *spi_info)
{
  uint32_t val;

  while(1) {
    val = readl(spi_info->ssp + STATUS);
    if((val & STATUS_BSY) == 0) {
      break;
    }
  }
}
static void
spi_wait_tx_fifo_empty(spi_info_t *spi_info)
{
  uint32_t val;

  while(1) {
    val = readl(spi_info->ssp + STATUS);
    writel(val, spi_info->ssp + STATUS);

    if((STATUS_TFL(val) == 0x0)
       && (STATUS_TNF(val) == 0x1)) {
      break;
    }
  }
}
static void
handle_tx(spi_info_t *spi_info, unsigned fifo_entry)
{
  struct spi_xfer_req *xfer_req = &spi_info->xfer_req;
  uint32_t data = 0;
  unsigned tx_size, tx_len, fifo_byte, copy;

  fifo_byte = fifo_entry * spi_info->fifo_dss;
  tx_size = (xfer_req->len_out > fifo_byte) ? fifo_byte : xfer_req->len_out;
  tx_len = tx_size;
  /*LOG_DBG("%s, entry = %u, byte = %u\n", __func__, fifo_entry, tx_size); */
  while(tx_size > 0) {
    copy = (tx_size > spi_info->fifo_dss) ? spi_info->fifo_dss : tx_size;
    if(xfer_req->out) {
      memcpy(&data, xfer_req->out, copy);
      xfer_req->out += copy;
    }
    writel(data, spi_info->ssp + DATAR);
    tx_size -= copy;
    /*LOG_DBG("<=== 0x%x, %d\n", (unsigned)data, tx_size); */
  }
  xfer_req->len_out -= tx_len;
  /*LOG_DBG("len_out: %d\n", xfer_req->len_out); */
  if((xfer_req->len_out == 0) && (spi_info->work_mode == WORK_MODE_IRQ)) {
    _tx_int_enable(spi_info, 0);
  }

  return;
}
static void
handle_rx(spi_info_t *spi_info, unsigned fifo_entry)
{
  struct spi_xfer_req *xfer_req = &spi_info->xfer_req;
  uint32_t data = 0;
  unsigned rx_size, rx_len, fifo_byte, copy;

  fifo_byte = fifo_entry * spi_info->fifo_dss;
  rx_size = (xfer_req->len_in > fifo_byte) ? fifo_byte : xfer_req->len_in;
  rx_len = rx_size;
  /*LOG_DBG("%s, entry = %u, byte = %u\n", __func__, fifo_entry, rx_size); */
  while(rx_size > 0) {
    data = readl(spi_info->ssp + DATAR);
    /*LOG_DBG("===> 0x%x\n", (unsigned)data); */
    copy = (rx_size > spi_info->fifo_dss) ? spi_info->fifo_dss : rx_size;
    if(xfer_req->in) {
      memcpy(xfer_req->in, &data, copy);
      xfer_req->in += copy;
    }
    rx_size -= copy;
  }
  xfer_req->len_in -= rx_len;
  if((xfer_req->len_in == 0) && (spi_info->work_mode == WORK_MODE_IRQ)) {
    _rx_int_enable(spi_info, 0);
  }

  return;
}
int
spi_set_dir(spi_bus_t bus, spi_dir_t dir)
{
  unsigned val;
  spi_info_t *spi_info;

  spi_info = get_spi_info(bus);
  if(!spi_info) {
    LOG_ERR("spi_set_dir failed\n");
    return -1;
  }

  val = readl(spi_info->ssp + THREE_WIRE_CTRL);
  if(dir == SPI_DIR_TX) {
    writel(val & (~THREE_WIRE_CTRL_TXD_OEN), spi_info->ssp + THREE_WIRE_CTRL);
  } else {
    writel(val | THREE_WIRE_CTRL_TXD_OEN, spi_info->ssp + THREE_WIRE_CTRL);
  }
  spi_info->txd_curr = dir;

  return 0;
}
static void
spi_set_wire_mode(spi_info_t *spi_info, spi_wire_mode_t wire_mode)
{
  unsigned val = 0;
  if(wire_mode == SPI_3_WIRE_MODE) {
    val = THREE_WIRE_CTRL_WIDTH_DYN_CHANGE | THREE_WIRE_CTRL_THREE_WIRE_EN;
  }
  writel(val, spi_info->ssp + THREE_WIRE_CTRL);
  /*LOG_INFO("spi_set_wire_mode, %d, 0x%x\n", wire_mode, readl(spi_info->ssp + THREE_WIRE_CTRL)); */
}
static void
spi_poll_handle(void *arg)
{
  spi_info_t *spi_info = (spi_info_t *)arg;
  uint32_t status;
  unsigned rx_level;
  int do_tx, do_rx;

  do_tx = 1;
  do_rx = 1;

  if(spi_mode_3_wire_tx(spi_info)) {
    do_rx = 0;
    spi_info->xfer_req.len_in = 0;
  }
  /*printf("spi_poll_handle\n"); */
  status = readl(spi_info->ssp + STATUS);
  writel(status, spi_info->ssp + STATUS);

  /*LOG_INFO("%s, status = 0x%x, do_tx = %d, do_rx = %d\n", __func__, (unsigned)status, do_tx, do_rx); */
  if(do_tx && (status & STATUS_TFS)) {  /* tx */
    handle_tx(spi_info, spi_info->fifo_size - spi_info->tx_fifo_thd);
  }

  if(do_rx && (status & STATUS_RFS)) {  /* rx */
    handle_rx(spi_info, spi_info->rx_fifo_thd);
  }

  if(status & STATUS_TINT) {  /* rx timeout(trailing byte) */
    if(spi_info->xfer_req.len_in <= (spi_info->rx_fifo_thd * spi_info->fifo_dss)) {
      rx_level = STATUS_RFL(status) + 1;
      if((rx_level != 32) || STATUS_RNE(status)) {  /* FIFO Not Empty */
        /*LOG_DBG("%s: trailing byte, %u, %u\n", __func__, rx_level, spi_info->xfer_req.len_in); */
        handle_rx(spi_info, rx_level);
      } else {
        ;/*LOG_DBG("%s: trailing byte, RX FIFO empty\n", __func__); */
      }
    } else {
      ;/*LOG_DBG("Rx len %u too large(rx to is too small), not trailing byte, skip\n", spi_info->xfer_req.len_in); */
    }
  }

  if(status & STATUS_TUR) {  /* error */
    LOG_ERR("%s: tx fifo underrun, status = 0x%x\n", __func__, (unsigned)status);
    spi_info->error = status;
  }

  if(status & STATUS_ROR) {  /* error */
    LOG_ERR("%s: rx fifo overrun, status = 0x%x\n", __func__, (unsigned)status);
    spi_info->error = status;
  }

  return;
}
static void
spi_xfer_complete(spi_info_t *spi_info)
{
  struct spi_xfer_req *xfer_req = &spi_info->xfer_req;

  spi_wait_tx_fifo_empty(spi_info);
  spi_wait_not_busy(spi_info);
  spi_port_enable(spi_info, 0);
  spi_int_enable(spi_info, 0);
  spi_rx_timeout_enable(spi_info, 0);
  spi_fifo_thd_enable(spi_info, 0);
  if(!xfer_req->cont) {
    spi_chip_sel(spi_info, 1);
  }
  if(spi_info->complete_cb) {
    spi_info->complete_cb(spi_info->cb_arg);
  }
}
static void
_spi_irq_handle(void *arg)
{
  spi_info_t *spi_info = (spi_info_t *)arg;
  uint32_t status;
  unsigned rx_level;
  int do_tx, do_rx;

  do_tx = 1;
  do_rx = 1;

  if(spi_mode_3_wire_tx(spi_info)) {
    do_rx = 0;
    spi_info->xfer_req.len_in = 0;
  }

  status = readl(spi_info->ssp + STATUS);
  writel(status, spi_info->ssp + STATUS);

  /*LOG_INFO("%s, status = 0x%x, do_tx = %d, do_rx = %d\n", __func__, (unsigned)status, do_tx, do_rx); */
  if(do_tx && (status & STATUS_TFS)) {  /* tx */
    handle_tx(spi_info, spi_info->fifo_size - spi_info->tx_fifo_thd);
  }

  if(do_rx && (status & STATUS_RFS)) {  /* rx */
    handle_rx(spi_info, spi_info->rx_fifo_thd);
  }

  if(status & STATUS_TINT) {  /* rx timeout(trailing byte) */
    if(spi_info->xfer_req.len_in <= (spi_info->rx_fifo_thd * spi_info->fifo_dss)) {
      rx_level = STATUS_RFL(status) + 1;
      if((rx_level != 32) || STATUS_RNE(status)) {  /* FIFO Not Empty */
        /*LOG_INFO("%s: trailing byte, %u, %u\n", __func__, rx_level, spi_info->xfer_req.len_in); */
        handle_rx(spi_info, rx_level);
      } else {
        ;/*LOG_INFO("%s: trailing byte, RX FIFO empty\n", __func__); */
      }
    } else {
      ;/*LOG_INFO("Rx len %u too large(rx to is too small), not trailing byte, skip\n", spi_info->xfer_req.len_in); */
    }
  }

  if(status & STATUS_TUR) {  /* error */
    LOG_ERR("%s: tx fifo underrun, status = 0x%x\n", __func__, (unsigned)status);
    spi_info->error = status;
  }

  if(status & STATUS_ROR) {  /* error */
    LOG_ERR("%s: rx fifo overrun, status = 0x%x\n", __func__, (unsigned)status);
    spi_info->error = status;
  }

  if(((spi_info->xfer_req.len_in == 0) && (spi_info->xfer_req.len_out == 0)) ||
     (spi_info->error != 0)) {
    spi_xfer_complete(spi_info);
  }
  return;
}
static void
spi_0_irq_handle(void *arg)
{
  spi_info_t *spi_info;
  (void)arg;

  spi_info = get_spi_info(SPI_BUS_SSP0);
  _spi_irq_handle(spi_info);
}
static void
spi_1_irq_handle(void *arg)
{
  spi_info_t *spi_info;
  (void)arg;

  spi_info = get_spi_info(SPI_BUS_SSP1);
  _spi_irq_handle(spi_info);
}
static void
spi_2_irq_handle(void *arg)
{
  spi_info_t *spi_info;
  (void)arg;

  spi_info = get_spi_info(SPI_BUS_SSP2);
  _spi_irq_handle(spi_info);
}
static spi_irq_handle_t spi_irq_handle[SPI_BUS_NUM] = {
  spi_0_irq_handle,
  spi_1_irq_handle,
  spi_2_irq_handle,
};
static int
_spi_pio_xfer_polling(spi_info_t *spi_info)
{
  struct spi_xfer_req *xfer_req = &spi_info->xfer_req;
  int ret = 0;

  /*LOG_INFO("%s\n", __func__); */

  spi_port_enable(spi_info, 1);

  while((xfer_req->len_in > 0) || (xfer_req->len_out > 0)) {
    spi_poll_handle(spi_info);
    if(spi_info->error) {
      LOG_ERR("%s failed, error code = 0x%x\n", __func__, spi_info->error);
      spi_info->error = 0;
      ret = -1;
      goto out;
    }
  }
  spi_wait_tx_fifo_empty(spi_info);
  spi_wait_not_busy(spi_info);
out:
  spi_port_enable(spi_info, 0);
  spi_rx_timeout_enable(spi_info, 0);
  spi_fifo_thd_enable(spi_info, 0);
  if(!xfer_req->cont) {
    spi_chip_sel(spi_info, 1);
  }

  if(ret < 0) {
    LOG_ERR("%s failed, len_in = %u, len_out = %u\n", __func__, xfer_req->len_in, xfer_req->len_out);
    return ret;
  }

  return ret;
}
static int
spi_pio_xfer(spi_info_t *spi_info)
{
  struct spi_xfer_req *xfer_req = &spi_info->xfer_req;
  int ret = -1;

  /*LOG_INFO("[%s]\n", __func__); */

  spi_calc_rx_fifo_thd(spi_info, xfer_req->len_in);
  spi_fifo_thd_enable(spi_info, 1);
  spi_rx_timeout_enable(spi_info, 1);

  if(xfer_req->mode == XFER_MODE_POLL) {
    ret = _spi_pio_xfer_polling(spi_info);
  } else if(xfer_req->mode == XFER_MODE_IRQ) {
    /*printf("spi_pio_xfer irq\n"); */
    spi_int_enable(spi_info, 1);
    spi_port_enable(spi_info, 1);
    ret = 0;
  } else {
    LOG_ERR("spi_pio_xfer: unknown mode %d\n", xfer_req->mode);
  }

  return ret;
}
static uint32_t tx_dma_dummy_data __attribute__((aligned(8)));
static uint32_t rx_dma_dummy_data __attribute__((aligned(8)));

static void
spi_fifo_dma_req_enable(spi_info_t *spi_info, int enable)
{
  uint32_t val;

  val = readl(spi_info->ssp + FIFO_CTRL);

  if(enable) {
    val |= FIFO_CTRL_RSRE | FIFO_CTRL_TSRE;
  } else {
    val &= ~(FIFO_CTRL_RSRE | FIFO_CTRL_TSRE);
  }

  writel(val, spi_info->ssp + FIFO_CTRL);
}
static int
_spi_dma_complete(void *arg)
{
  spi_info_t *spi_info = (spi_info_t *)arg;
  struct spi_xfer_req *xfer_req = &spi_info->xfer_req;

  /*printf("spi_dma_complete\n"); */

  if(!spi_mode_3_wire_tx(spi_info)) {
    spi_rx_timeout_enable(spi_info, 0);
  }
  spi_wait_tx_fifo_empty(spi_info);
  spi_wait_not_busy(spi_info);
  spi_fifo_dma_req_enable(spi_info, 0);
  spi_fifo_thd_enable(spi_info, 0);
  spi_port_enable(spi_info, 0);
  if(!xfer_req->cont) {
    spi_chip_sel(spi_info, 1);
  }
  if(spi_info->complete_cb) {
    spi_info->complete_cb(spi_info->cb_arg);
  }
  return 0;
}
static int
spi_0_dma_complete(void *arg)
{
  spi_info_t *spi_info;
  (void)arg;

  spi_info = get_spi_info(SPI_BUS_SSP0);
  return _spi_dma_complete(spi_info);
}
static int
spi_1_dma_complete(void *arg)
{
  spi_info_t *spi_info;
  (void)arg;

  spi_info = get_spi_info(SPI_BUS_SSP1);
  return _spi_dma_complete(spi_info);
}
static int
spi_2_dma_complete(void *arg)
{
  spi_info_t *spi_info;
  (void)arg;

  spi_info = get_spi_info(SPI_BUS_SSP2);
  return _spi_dma_complete(spi_info);
}
static dma_ch_complete_t spi_dma_handle[SPI_BUS_NUM] = {
  spi_0_dma_complete,
  spi_1_dma_complete,
  spi_2_dma_complete,
};
static void
config_dma_tx_channel(spi_info_t *spi_info, const void *tx_buf, unsigned len)
{
  unsigned data_dma;
  struct dma_op_t *op = &spi_info->tx_dma_op;

  dmac_map_device_to_channel(spi_info->tx_dma_dev_id, DMA_CHANNEL_SPI_TX);

  if(tx_buf) {
    data_dma = dma_mapped_addr((unsigned)tx_buf);
    op->inc_src_addr = 1;
  } else {
    data_dma = dma_mapped_addr((unsigned)&tx_dma_dummy_data);
    op->inc_src_addr = 0;
  }

  op->channel = DMA_CHANNEL_SPI_TX;
  op->src = data_dma;
  op->dest = spi_info->ssp + DATAR;
  op->len = len;
  op->width = 4; /* 4 bytes */
  op->burst_size = DMA_BURST_SIZE_32_BYTE;
  op->flow_dest = 1;
  op->flow_src = 0;
  op->inc_dest_addr = 0;

  dma_register_ch_cb(op->channel, spi_dma_handle[spi_info->bus], spi_info);
  dma_xfer(op); /* wait for request */

  return;
}
static void
config_dma_rx_channel(spi_info_t *spi_info, const void *rx_buf, unsigned len)
{
  unsigned data_dma;
  struct dma_op_t *op = &spi_info->rx_dma_op;

  dmac_map_device_to_channel(spi_info->rx_dma_dev_id, DMA_CHANNEL_SPI_RX);

  if(rx_buf) {
    data_dma = dma_mapped_addr((unsigned)rx_buf);
    op->inc_dest_addr = 1;
  } else {
    data_dma = dma_mapped_addr((unsigned)&rx_dma_dummy_data);
    op->inc_dest_addr = 0;
  }

  op->channel = DMA_CHANNEL_SPI_RX;
  op->src = spi_info->ssp + DATAR;
  op->dest = data_dma;
  op->len = len;
  op->width = 4; /* 4 bytes */
  op->burst_size = DMA_BURST_SIZE_32_BYTE;
  op->flow_dest = 0;
  op->flow_src = 1;
  op->inc_src_addr = 0;

  dma_xfer(op); /* wait for request */
}
/* The data should be 8 bytes aligned, len should be 4 bytes aligned */
static int
spi_dma_xfer(spi_info_t *spi_info)
{
  struct spi_xfer_req *xfer_req = &spi_info->xfer_req;
  int ret = 0;
  int do_tx, do_rx;

  do_tx = 1;
  do_rx = 1;

  if(spi_mode_3_wire_tx(spi_info)) {
    do_rx = 0;
    spi_info->xfer_req.len_in = 0;
  }
  dma_register_ch_cb(DMA_CHANNEL_SPI_TX, spi_dma_handle[spi_info->bus], spi_info);
  spi_info->work_mode = WORK_MODE_DMA;
  if(do_tx) {
    config_dma_tx_channel(spi_info, xfer_req->out, xfer_req->len_out);
  }
  if(do_rx) {
    config_dma_rx_channel(spi_info, xfer_req->in, xfer_req->len_in);
    spi_calc_rx_fifo_thd(spi_info, xfer_req->len_in);
    spi_rx_timeout_enable(spi_info, 1);
  }
  spi_fifo_dma_req_enable(spi_info, 1);
  spi_fifo_thd_enable(spi_info, 1);
  spi_port_enable(spi_info, 1);

  if(ret < 0) {
    LOG_ERR("%s failed,error = 0x%x\n", __func__, spi_info->error);
    return ret;
  }

  return ret;
}
static const struct spi_clk_info spi_clk_cfg[SPI_CLK_MAX] = {
  { /* SPI_CLK_6500KHZ */
    .clk_val = 0,
    .desc = "6.5MHz",
  },
  { /* SPI_CLK_13MHZ */
    .clk_val = 1,
    .desc = "13MHz",
  },
  { /* SPI_CLK_26MHZ */
    .clk_val = 2,
    .desc = "26MHz",
  },
  { /* SPI_CLK_52MHZ */
    .clk_val = 3,
    .desc = "52MHz",
  },
  { /* SPI_CLK_3250HZ */
    .clk_val = 4,
    .desc = "3.25MHz",
  },
  { /* SPI_CLK_1625KHZ */
    .clk_val = 5,
    .desc = "1.625MHz",
  },
  { /* SPI_CLK_812500HZ */
    .clk_val = 6,
    .desc = "812.5KHz",
  },
  { /* SPI_CLK_1MHZ */
    .clk_val = 7,
    .desc = "1MHz",
  },
};

static int
spi_clk_init(spi_info_t *spi_info, spi_clk_t clk)
{
  const struct spi_clk_info *pclk;

  if(clk >= SPI_CLK_MAX) {
    LOG_ERR("Invalid clk %d\n", clk);
    goto error;
  }

  pclk = &spi_clk_cfg[clk];
  writel(RST(1), spi_info->clk_reg);
  mdelay(1); /* > 10us */
  writel(APBCLK(1) | FNCLK(1) | FNCLKSEL(pclk->clk_val), spi_info->clk_reg);
  spi_info->clk = clk;

  /*LOG_INFO("spi_clk_init: (%d, %s): ok\n", spi_info->bus, pclk->desc); */
  return 0;
error:
  LOG_ERR("%s(%d, %u) failed\n", __func__, spi_info->bus, clk);
  return -1;
}
int
spi_set_clk(spi_bus_t bus, spi_clk_t clk)
{
  spi_info_t *spi_info;
  const struct spi_clk_info *pclk;
  uint32_t val;

  spi_info = get_spi_info(bus);
  if(!spi_info) {
    goto error;
  }
  if(spi_info->clk != clk) {
    val = readl(spi_info->clk_reg);
    pclk = &spi_clk_cfg[clk];
    val = (val & ~FNCLKSEL_MASK) | FNCLKSEL(pclk->clk_val);
    writel(val, spi_info->clk_reg);
    /*LOG_INFO("spi_set_clk(%s), reg = 0x%x\n", pclk->desc, readl(spi_info->clk_reg)); */
    spi_info->clk = clk;
  }

  return 0;
error:
  LOG_ERR("%s failed\n", __func__);
  return -1;
}
int
spi_register_complete_cb(spi_bus_t bus, spi_complete_cb_t cb, void *arg)
{
  spi_info_t *spi_info;

  spi_info = get_spi_info(bus);
  if(!spi_info) {
    LOG_ERR("%s failed\n", __func__);
    return -1;
  }
  spi_info->complete_cb = cb;
  spi_info->cb_arg = arg;
  return 0;
}
static void
spi_reset(spi_info_t *spi_info, int master_mode, int spi_mode)
{
  uint32_t val = 0;

  if(master_mode == 0) {
    val = TOP_CTRL_SFRMDIR | TOP_CTRL_SCLKDIR;
  }

  val |= TOP_TTE; /* TOP_TTELP should not set for SPI according to datasheet */
  if(spi_mode & 0x1) { /* CPHA */
    val |= TOP_SPH;
  }
  if(spi_mode & 0x2) { /* CPOL */
    val |= TOP_SPO;
  }

  writel(val, spi_info->ssp + TOP_CTRL);
  spi_fifo_thd_enable(spi_info, 1);
}
int
spi_transfer_bytes(spi_bus_t bus, bool cont, const void *out,
                   void *in, size_t len, xfer_mode_t mode)
{
  int ret = 0;
  spi_info_t *spi_info;
  struct spi_xfer_req *xfer_req;
  unsigned len_b, len_w;

  spi_info = get_spi_info(bus);
  if(!spi_info) {
    goto error;
  }

  spi_chip_sel(spi_info, 0);
  if(len) {
    len_b = len % 4;
    len_w = len / 4;
    xfer_req = &spi_info->xfer_req;

    if(len_b) {
      xfer_req->in = in;
      xfer_req->out = out;
      xfer_req->len_out = len_b;
      xfer_req->len_in = len_b;
      xfer_req->cont = cont;
      xfer_req->mode = XFER_MODE_POLL;
      spi_set_xfer_endian(spi_info, 0, 0);
      spi_set_fifo_dss(spi_info, 1);
      ret = spi_pio_xfer(spi_info);
    }

    if(len_w) {
      xfer_req->in = (uint8_t *)in + len_b;
      xfer_req->out = (uint8_t *)out + len_b;
      xfer_req->len_out = len - len_b;
      xfer_req->len_in = len - len_b;
      xfer_req->cont = cont;
      /*LOG_DBG("spi_transfer_bytes: out: %d, in: %d\n", xfer_req->len_out, xfer_req->len_in); */
      xfer_req->mode = (mode == XFER_MODE_IRQ) ? XFER_MODE_IRQ : XFER_MODE_POLL;
      spi_set_xfer_endian(spi_info, 2, 2);
      spi_set_fifo_dss(spi_info, 4);

      ret = spi_pio_xfer(spi_info);
    }
  }

  return ret;
error:
  LOG_ERR("%s(%d, %d, %p, %p, %u) failed\n", __func__, bus, cont, out, in, len);
  return -1;
}
/* out and in should be 8 bytes aligned, len should be 4 bytes aligned */
static int
spi_transfer_bytes_dma(spi_bus_t bus, bool cont, const void *out, void *in, size_t len)
{
  int ret = 0;
  spi_info_t *spi_info;
  struct spi_xfer_req *xfer_req;

  spi_info = get_spi_info(bus);
  if(!spi_info || (spi_info->bus < 0)) {
    LOG_ERR("Spi %d is invalid or not inited\n", bus);
    goto error;
  }

  if(out && ((unsigned long)out & 0x7)) {
    LOG_ERR("Out buf addr %p is not 8 byte aligned\n", out);
    goto error;
  }

  if(in && ((unsigned long)in & 0x7)) {
    LOG_ERR("In buf addr %p is not 8 byte aligned\n", in);
    goto error;
  }

  if(len & 0x3) {
    LOG_ERR("Len %u is not 4 byte aligned\n", len);
    goto error;
  }

  /*LOG_INFO("%s(%d, %d, %p, %p, %u)\n", __func__, bus, cont, out, in, len); */

  if(len == 0) {
    return 0;
  }

  xfer_req = &spi_info->xfer_req;

  xfer_req->in = in;
  xfer_req->out = out;
  xfer_req->len_in = xfer_req->len_out = len;
  xfer_req->cont = cont;

  spi_chip_sel(spi_info, 0);
  spi_set_fifo_dss(spi_info, 4);
  spi_set_xfer_endian(spi_info, 2, 2); /* For FIFO dss == 4 */
  ret = spi_dma_xfer(spi_info);

  return ret;
error:
  LOG_ERR("%s(%d, %d, %p, %p, %u) failed\n", __func__, bus, cont, out, in, len);
  return -1;
}
int
spi_arch_init(spi_param_t *arg)
{
  spi_info_t *spi_info;

  spi_info = get_spi_info(arg->bus);
  if(!spi_info) {
    goto error;
  }

  if(arg->mfp_cfg) {
    spi_info->mfp_cfg = arg->mfp_cfg;
  }
  if(!spi_info->mfp_cfg) {
    LOG_ERR("miss mfp config\n");
    goto error;
  }
  mfp_config((uint32_t *)spi_info->mfp_cfg);
  spi_info->gpio_cs = arg->cs_pin;

  if(spi_info->gpio_cs != GPIO_INVALID) {
    gpio_set_direction(spi_info->gpio_cs, GPIO_OUT);
    gpio_output_set(spi_info->gpio_cs, 1);
  }

  spi_info->bus = arg->bus;
  spi_info->fifo_size = FIFO_ENTRY_MAX;
  spi_info->rx_fifo_thd = RX_THRESHOLD;
  spi_info->tx_fifo_thd = TX_THRESHOLD;
  spi_info->wire_mode = arg->wire_mode;
  if(spi_info->irq != 0) {
    register_int_handler(spi_info->irq, spi_irq_handle[arg->bus], NULL);
    unmask_interrupt(spi_info->irq);
  }
  spi_clk_init(spi_info, arg->clk);
  spi_reset(spi_info, 1, arg->spi_mode); /* master mode */
  spi_set_wire_mode(spi_info, arg->wire_mode);

  /*dma_init(); */
  LOG_ERR("Spi int OK!\n");

  return 0;
error:
  LOG_ERR("spi_arch_init failed!\n");
  return -1;
}
/* (bits >= 1) && (bits <= 8) */
int
_spi_transfer_bits(spi_bus_t bus, bool cont, const void *out, void *in, size_t bits)
{
  int ret = 0, do_tx = 1, do_rx = 1;
  spi_info_t *spi_info;
  uint32_t status, data = 0, val;

  /*LOG_INFO("_spi_transfer_bits, bits = %d\n", bits); */
  spi_info = get_spi_info(bus);
  if(!spi_info || (spi_info->bus < 0)) {
    LOG_ERR("Spi %d is invalid or not inited\n", bus);
    goto error;
  }

  spi_chip_sel(spi_info, 0);
  if(bits) {
    spi_set_xfer_endian(spi_info, 0, 0);
    spi_set_fifo_dss_bit(spi_info, bits);

    /* polling mode */
    val = readl(spi_info->ssp + INT_EN);
    val &= ~(INT_EN_TIE | INT_EN_RIE);
    writel(val, spi_info->ssp + INT_EN);
    /* enable port */
    spi_port_enable(spi_info, 1);
    if(spi_mode_3_wire_tx(spi_info)) {
      do_rx = 0;
    }
    while(do_tx || do_rx) {
      status = readl(spi_info->ssp + STATUS);
      writel(status, spi_info->ssp + STATUS);
      if(do_tx && STATUS_TNF(status)) {
        do_tx = 0;
        data = 0;
        if(out) {
          memcpy(&data, out, 1);
        }
        writel(data, spi_info->ssp + DATAR);
        /*LOG_INFO("bits <=== 0x%x\n", (unsigned)data); */
      }

      if(do_rx && STATUS_RNE(status)) {
        do_rx = 0;
        data = readl(spi_info->ssp + DATAR);
        /*LOG_INFO("bits ===> 0x%x\n", (unsigned)data); */
        if(in) {
          memcpy(in, &data, 1);
        }
      }
    }
    spi_wait_tx_fifo_empty(spi_info);
    spi_wait_not_busy(spi_info);
    spi_port_enable(spi_info, 0);
  }
  if(!cont) {
    spi_chip_sel(spi_info, 1);
  }

  return ret;
error:
  LOG_ERR("%s(%d, %d, %p, %p, %u) failed\n", __func__, bus, cont, out, in, bits);
  return -1;
}
int
spi_transfer_bits(spi_bus_t bus, bool cont, const void *out,
                  void *in, size_t bits, xfer_mode_t mode)
{
  size_t byte, bit;
  int ret = 0;
  uint8_t val = 0;

  byte = bits / 8;
  bit = bits % 8;
  bool cont_byte = (bit != 0) ? true : cont;

  /*LOG_INFO("spi_transfer_bits, byte = %d, bit = %d\n", byte, bit); */

  if(byte) {
    if(mode == XFER_MODE_DMA) {
      val = spi_transfer_bytes_dma(bus, cont_byte, out, in, byte);
    } else {
      val = spi_transfer_bytes(bus, cont_byte, out, in, byte, mode);
    }
  }

  if(bit) {
    if(out) {
      val = *((uint8_t *)out + byte);
      val = val >> (8 - bit);
    }
    ret = _spi_transfer_bits(bus, cont, (void *)&val, in, bit);
  }

  return ret;
}
int
spi_chip_select(spi_bus_t bus, int state)
{
  spi_info_t *spi_info = get_spi_info(bus);
  return spi_chip_sel(spi_info, state);
}
#endif /* LCD_GENERIC_SPI */
